const vscode = require('vscode');
const child = require('child_process');
const fetch = require('node-fetch').default;
const treeView = require('./src/treeView');
const moment = require('moment');
const { getRootPath, getSettingConfig, getIssuesFsPath, fixFilePathPre, setSettingsPassword } = require('./src/utils');

/**
 * @param {vscode.ExtensionContext} context
 */
function activate(context) {
	let globalScanFileMap = {};
	let currentIssues = 'all';
	let loginReady = false;

	// 注册输出管道
	const outputChannel = vscode.window.createOutputChannel('OperatorLink');
	channelLog('extension "operatorlint" is now active!');

	// 注册 TreeViewProvider
	const issuesTree = new treeView.IssuesTreeViewProvider();
	const hotspotsTree = new treeView.HotspotsTreeViewProvider();
	const issuesView = vscode.window.createTreeView('issues', { treeDataProvider: issuesTree });
	const hotspotsView = vscode.window.createTreeView('hotspots', { treeDataProvider: hotspotsTree });

	// 监听treeview点击事件打开文件
	issuesView.onDidChangeSelection(openFile);
	hotspotsView.onDidChangeSelection(openFile);

	// 注册扫描事件/接受扫描结果/打开window
	const cppcheckStart = vscode.commands.registerCommand('operatorlint.startScanning', triggerScan);
	const scanResults = vscode.commands.registerCommand('operatorlint.scanResults', () => reciveScanResult());
	const openWin = vscode.commands.registerCommand('operatorlint.openWindow', operatorWin);
	const openInput = vscode.commands.registerCommand('operatorlint.openInput', operatorInput);
	const assignMy = vscode.commands.registerCommand('operatorlint.assignMy', () => updateIssuesType('my'));
	const assignAll = vscode.commands.registerCommand('operatorlint.assignAll', () => updateIssuesType('all'));

	// 注册代码提示集合
	const collection = vscode.languages.createDiagnosticCollection('issuesmessage');
	const changeAcitveEditor = vscode.window.onDidChangeActiveTextEditor(editor => editor && updateDiagnostics(editor.document));
	const changeSettingsSub = vscode.workspace.onDidChangeConfiguration(settingsChange);

	context.subscriptions.push(cppcheckStart);
	context.subscriptions.push(scanResults);
	context.subscriptions.push(changeAcitveEditor);
	context.subscriptions.push(openWin);
	context.subscriptions.push(openInput);
	context.subscriptions.push(assignMy);
	context.subscriptions.push(assignAll);
	context.subscriptions.push(collection);
	context.subscriptions.push(changeSettingsSub);


	init();


	//
	// ---------------上面入口函数-----下面执行函数-----请遵守规则合理拆分代码文件-----------------------
	//


	/**
	 * 入口
	 */
	async function init() {
		await updateIssuesType('all');
	}

	/**
	 * 监听settings.json文件变化
	 */
	async function settingsChange(e) {
		if (e.affectsConfiguration('operatorlint.username') || e.affectsConfiguration('operatorlint.url')) {
			loginReady = false;
		}
	}

	/**
	 * 鉴权
	 * @returns {promise}
	 */
	async function passAuth(password) {
		let result = null;
		const { username, url } = getSettingConfig();
		const formData = new URLSearchParams();

		formData.append('login', username);
		formData.append('password', password);

		try {
			const res = await fetch(url + '/api/authentication/login', {
				method: 'post',
				body: formData,
			});

			if (!res.ok) {
				result = res.statusText || 'login failed';

				channelLog('login failed:' + res.status + res.statusText);
			}
		} catch (e) {
			result = 'Network failure or request blocked';

			channelLog('login failed:' + e?.message);
		}

		return result;
	}

	/**
	 * 输出日志到output
	 * @param {*} message 
	 */
	function channelLog(message) {
		outputChannel.appendLine(message);
		outputChannel.show();
	}

	/**
	 * 更新context
	 * @param {('my' | 'all')} type 
	 */
	async function updateIssuesType(type) {
		currentIssues = type;
		await vscode.commands.executeCommand('setContext', 'operatorlint.issues.assign', type);

		reciveScanResult();
	}

	/**
	 * 打开全局输入框
	 */
	function operatorInput() {
		const { username, url } = getSettingConfig();

		return new Promise((resolve, reject) => {
			const inputBox = vscode.window.createInputBox();

			inputBox.title = 'operator-lint';
			inputBox.prompt = `Enter password for ${username}, ip ${url}`;
			inputBox.ignoreFocusOut = true;
			inputBox.password = true;
			inputBox.placeholder = `Enter password for ${username}, ip ${url}`;
			inputBox.onDidAccept(async () => {
				const value = inputBox.value;

				if (!value) {
					inputBox.validationMessage = 'Password can not be blank';

					return;
				}

				const result = await passAuth(value);

				if (result) {
					inputBox.validationMessage = result;
				} else {
					setSettingsPassword(value);

					resolve();
					inputBox.dispose();
				}
			});
			
			inputBox.onDidHide(() => {
				reject();
			});

			inputBox.show();
		});
	}

	/**
	 * 打开webview
	 */
	function operatorWin() {
		const settingsConfg = getSettingConfig();
		const panel = vscode.window.createWebviewPanel(
			'operatorlintWin',
			'operatorlint window',
			vscode.ViewColumn.One,
			{ enableScripts: true }
		);

		panel.webview.html = getWebviewContent();

		function getWebviewContent() {
			return `<!DOCTYPE html>
					<html lang="en">
						<head>
							<meta charset="UTF-8">
							<meta name="viewport" content="width=device-width, initial-scale=1.0">
							<title>operatorlint</title>
						</head>
						<script>
							// window.location.href = '${settingsConfg.url}';
						</script>
						<body>
							<h1 style="text-align: center">建设中。</h1>
						</body>
					</html>`;
		}
	}

	/**
	 * 代码错误/警告提示
	 * @param {*} document 
	 */
	function updateDiagnostics(document) {
		const diagIssues = globalScanFileMap[document.uri.query ? JSON.parse(document.uri.query).path : document.uri.fsPath];

		if (diagIssues) {
			collection.set(document.uri, diagIssues.map(i => ({
				code: '',
				message: i.message,
				range: new vscode.Range(new vscode.Position(i.textRange.startLine - 1, i.textRange.startOffset), new vscode.Position(i.textRange.endLine - 1, i.textRange.endOffset)),
				severity: vscode.DiagnosticSeverity.Warning,
				source: '',
			})));
		} else {
			collection.clear();
		}
	}

	/**
	 * 触发远端扫描项目
	 * @param {{fsPath: string}} [param] 菜单右键会有传参传入
	 */
	async function triggerScan(param) {
		await checkProjectInit();

		const workspaceFolderPath = getRootPath();
		const { username, password, url, projectkey } = getSettingConfig();
		const fsPath = param?.fsPath ? param.fsPath.replace(workspaceFolderPath, '').replaceAll('\\', '/').substr(1) : '.';
		const exeCode = `bash ${workspaceFolderPath}/codecheck.sh ${projectkey} ${url} ${username} ${password} ${fsPath}`;

		vscode.window.showInformationMessage('Manual trigger scan successful.');
		channelLog('sh exec.');

		const childProcess = child.exec(exeCode, { cwd: workspaceFolderPath });

		childProcess.stdout.on('data', data => {
			const now = moment();

			channelLog((`[${now.format('YYYY-MM-DD hh:mm:ss')} STDOUT]` + data).trim());
		});

		childProcess.stderr.on('data', data => {
			const now = moment();

			channelLog((`[${now.format('YYYY-MM-DD hh:mm:ss')} STDERR]` + data).trim());
		});

		childProcess.on('exit', data => {
			const now = moment();

			channelLog((`[${now.format('YYYY-MM-DD hh:mm:ss')} EXIT]` + data).trim());
			reciveScanResult();
		});
	}

	/**
	 * 确认项目初始化所必须的参数
	 * @returns {Promise}
	 */
	async function checkProjectInit() {
		const rootPath = getRootPath();
		const { username, url, projectkey } = getSettingConfig();

		if (!username || !url || !projectkey) {
			vscode.window.showErrorMessage('Please configure ".vscode/settings.json operatorlint.username operatorlint.url operatorlint.projectkey" parameters.');

			return Promise.reject();
		}

		if (!rootPath) {
			vscode.window.showErrorMessage('Please open the project file.');

			return Promise.reject();
		}

		if (!loginReady) {
			return await operatorInput();
		}

		return Promise.resolve();
	}

	/**
	 * 接收扫描结果
	 */
	async function reciveScanResult() {
		await checkProjectInit();

		const { username, password, url, projectkey } = getSettingConfig();

		let issuesUrl = url + '/api/issues/search?resolved=false&s=FILE_LINE&componentKeys=' + projectkey + '&p=1&ps=500';
		let hotspotsUrl = url + '/api/hotspots/search?status=TO_REVIEW&projectKey=' + projectkey + '&p=1&ps=500';

		if (currentIssues === 'my') {
			issuesUrl += '&assignees=__me__';
			hotspotsUrl += '&onlyMine=true';
		}

		channelLog('start recive results.');

		let resBody;

		try {
			resBody = await Promise.all([
				fetch(issuesUrl, {
					method: 'get',
					headers: {
						'Authorization': 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64')
					}
				}),
				fetch(hotspotsUrl, {
					method: 'get',
					headers: {
						'Authorization': 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64')
					}
				})
			]);
		} catch (e) {
			channelLog('recive results error.');
			channelLog(e);

			loginReady = false;
		}

		if (resBody[0].ok) {
			channelLog('Successfully obtained scan results.');

			loginReady = true;
		} else {
			channelLog('recive results error.');
			channelLog(resBody[0].statusText + resBody[1].statusText);

			loginReady = false;
		}

		const res = [];
		res[0] = await resBody?.[0]?.json();
		res[1] = await resBody?.[1]?.json();

		const issuesData = res?.[0]?.issues || [];
		const hotspotsData = res?.[1]?.hotspots || [];
		const issuesResult = [];
		const hotspotsResult = [];
		const issuesMap = {};
		const hotspotsMap = {};
		globalScanFileMap = {};

		issuesData.forEach(i => {
			if (!issuesMap[i.type]) {
				issuesMap[i.type] = {};
			}

			if (!issuesMap[i.type][i.component]) {
				issuesMap[i.type][i.component] = [];
			}

			issuesMap[i.type][i.component].push(i);
		});

		hotspotsData.forEach(i => {
			if (hotspotsMap[i.component]) {
				hotspotsMap[i.component].push(i);
			} else {
				hotspotsMap[i.component] = [i];
			}
		});

		Object.keys(issuesMap).forEach(key => {
			const parentItem = {
				label: key,
				children: []
			};

			issuesResult.push(parentItem);

			Object.keys(issuesMap[key]).forEach(i => {
				parentItem.children.push({
					label: fixFilePathPre(i),
					children: issuesMap[key][i].map(j => ({ ...j, label: j.message }))
				});

				if (!globalScanFileMap[getIssuesFsPath(i)]) globalScanFileMap[getIssuesFsPath(i)] = [];

				globalScanFileMap[getIssuesFsPath(i)].push(...issuesMap[key][i].filter(j => j.textRange));
			});
		});

		Object.keys(hotspotsMap).forEach(i => {
			hotspotsResult.push({
				label: fixFilePathPre(i),
				children: hotspotsMap[i].map(j => ({ ...j, label: j.message }))
			});

			if (!globalScanFileMap[getIssuesFsPath(i)]) globalScanFileMap[getIssuesFsPath(i)] = [];

			globalScanFileMap[getIssuesFsPath(i)].push(...hotspotsMap[i].filter(j => j.textRange));
		});

		issuesTree.refresh(issuesResult);
		hotspotsTree.refresh(hotspotsResult);

		if (vscode.window.activeTextEditor) updateDiagnostics(vscode.window.activeTextEditor.document);
	}

	/**
	 * 移动光标到issues代码
	 * @param {*} textRange 
	 */
	function cursorToRange(textRange) {
		if (vscode.window.activeTextEditor) {
			vscode.window.activeTextEditor.revealRange(
				new vscode.Range(new vscode.Position(textRange.endLine - 1, 0), new vscode.Position(textRange.endLine - 1, 0)),
				vscode.TextEditorRevealType.InCenter
			);

			vscode.window.activeTextEditor.selection = new vscode.Selection(
				new vscode.Position(textRange.endLine - 1, textRange.endOffset),
				new vscode.Position(textRange.endLine - 1, textRange.endOffset)
			);
		}
	}

	/**
	 * 执行打开文件操作
	 * @param {*} e 
	 */
	function openFile(e) {
		if (!e?.selection?.[0]?.children) {
			const rootPath = getRootPath();
			const config = getSettingConfig();
			const currentIssue = e.selection[0];

			vscode.workspace
				.openTextDocument(rootPath + '/' + currentIssue.component.replace(config.projectkey + ':', ''))
				.then(async (doc) => {
					await vscode.window.showTextDocument(doc);
					currentIssue?.textRange && cursorToRange(currentIssue.textRange);
				});
		}
	};
}

// This method is called when your extension is deactivated
function deactivate() { }

module.exports = {
	activate,
	deactivate
}
